﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using Microsoft.Practices.Unity;

namespace TestByWire
{
    public class Recorder
    {
        internal static IList<IInvocationUpdater> _recordings = new List<IInvocationUpdater>();

        [ThreadStatic]
        private static IUnityContainer _container;

        internal static void Reset(IUnityContainer container)
        {
            _recordings.Clear();
            _container = container;
        }

        internal static void Transfer()
        {
            foreach (var recording in _recordings)
            {
                recording.Transfer(_container);
            }
        }
    }

    public class Given : Recorder
    {
        public static Invocation<T, TResult> That<T, TResult>(Expression<Func<T, TResult>> func) where T : class
        {
            return That(null, func);
        }

        public static Invocation<T, TResult> That<T, TResult>(String name, Expression<Func<T, TResult>> func) where T : class
        {
            if (Scenario.IsRecording())
                throw new InvalidContextException("Given statements cannot be part of external scenario");

            Invocation<T, TResult> invocation = Invocation.Create(func, name, Priority.High);
            foreach (var item in _recordings)
                item.UpdateRepeatCount(invocation);
            _recordings.Add(invocation);

            return invocation;
        }

        public static Invocation<T, object> That<T>(Expression<Action<T>> action) where T : class
        {
            return That(null, action);
        }

        public static Invocation<T, object> That<T>(String name, Expression<Action<T>> action) where T : class
        {
            if (Scenario.IsRecording())
                throw new InvalidContextException("Given statements cannot be part of external scenario");

            Invocation<T, object> invocation = Invocation.Create(action, name, Priority.High);
            foreach (var item in _recordings)
                item.UpdateRepeatCount(invocation);
            _recordings.Add(invocation);
            return invocation;
        }

        public static void Fake<T>(T fakeInstance) where T : class
        {
            if (Scenario.IsRecording())
                throw new InvalidContextException("Given statements cannot be part of external scenario");

            if (fakeInstance == null)
                throw new ArgumentNullException("fakeInstance");

            IContainerInteraction replaceInteraction = null;

            foreach (var existingInteraction in DynamicDeclarations.GetDynamicInteractions())
            {
                if (existingInteraction.GetType() == typeof(UseFakeInteraction) &&
                    existingInteraction.RegistrationType == typeof(T))
                {
                    replaceInteraction = existingInteraction;
                    break;
                }
            }

            if (replaceInteraction == null)
            {
                DynamicDeclarations.AddContainerInteraction(new UseFakeInteraction(typeof(T), fakeInstance));
            }
            else
            {
                DynamicDeclarations.ReplaceContainerInteraction(replaceInteraction, new UseFakeInteraction(typeof(T), fakeInstance));
            }
        }

        public static void FakeLateBound<T>(string fieldName, T fakeInstance)
        {
            if (string.IsNullOrEmpty(fieldName))
            {
                throw new ArgumentNullException("fieldName");
            }

            if (Scenario.IsRecording())
                throw new InvalidContextException("Given statements cannot be part of external scenario");

            IContainerInteraction replaceInteraction = null;

            foreach (var existingInteraction in DynamicDeclarations.GetDynamicInteractions())
            {
                if (existingInteraction.GetType() == typeof(UseFakeLateBoundInteraction) &&
                    existingInteraction.RegistrationName == fieldName)
                {
                    replaceInteraction = existingInteraction;
                    break;
                }
            }

            if (replaceInteraction == null)
            {
                DynamicDeclarations.AddContainerInteraction(new UseFakeLateBoundInteraction(fieldName, fakeInstance));
            }
            else
            {
                DynamicDeclarations.ReplaceContainerInteraction(replaceInteraction, new UseFakeLateBoundInteraction(fieldName, fakeInstance));
            }
        }
    }
}

